from django.db import models
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
import random

""" Return the first element of a QuerySet or None """
def fst_qs(qs): return qs[:1][0] if qs.count() else None

""" Return a random element from a QuerySet or None """
def rnd_qs(qs): 
    n = qs.count()
    return qs.all()[random.randint(0, n-1)] if n else None

""" Return the first found instance of a model or None """
def fst(M, **kwargs):
    order_by = kwargs.pop("order_by", None)
    qs = M.objects.filter(**kwargs)
    if order_by: qs = qs.order_by(order_by)
    return fst_qs(qs)

""" Return a random instance of a model or None """
def rnd(M, **kwargs): return rnd_qs(M.objects.filter(**kwargs))

def _nest_help(obj, depth, val):
    current = obj
    for i in range(depth):
        current = current[-1]
    current.append(val)

def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
    """
    Helper function that recursively populates deleted_objects.

    `levels_to_root` defines the number of directories (../) to reach the
    admin root path. In a change_view this is 4, in a change_list view 2.

    This is for backwards compatibility since the options.delete_selected
    method uses this function also from a change_list view.
    This will not be used if we can reverse the URL.
    """
    nh = _nest_help # Bind to local variable for performance
    if current_depth > 16:
        return # Avoid recursing too deep.
    opts_seen = []
    for related in opts.get_all_related_objects():
        if related.opts in opts_seen:
            continue
        opts_seen.append(related.opts)
        rel_opts_name = related.get_accessor_name()
        if isinstance(related.field.rel, models.OneToOneRel):
            try:
                sub_obj = getattr(obj, rel_opts_name)
            #except ObjectDoesNotExist:
            except:
                pass
            else:
                nh(deleted_objects, current_depth,
                    [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
                get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
        else:
            has_related_objs = False
            for sub_obj in getattr(obj, rel_opts_name).all():
                has_related_objs = True
                nh(deleted_objects, current_depth, [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
                get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
    for related in opts.get_all_related_many_to_many_objects():
        if related.opts in opts_seen:
            continue
        opts_seen.append(related.opts)
        rel_opts_name = related.get_accessor_name()
        has_related_objs = False

        # related.get_accessor_name() could return None for symmetrical relationships
        if rel_opts_name:
            rel_objs = getattr(obj, rel_opts_name, None)
            if rel_objs:
                has_related_objs = True

        if has_related_objs:
            for sub_obj in rel_objs.all():
                nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])

def deleted_objects_list(obj, user):
    deleted_objects = [mark_safe(u'%s: %s' % (escape(force_unicode(capfirst(obj._meta.verbose_name))), escape(obj))), []]
    perms = set()
    get_deleted_objects(deleted_objects, perms, user, obj, obj._meta, 1)
    return deleted_objects

